#! /usr/bin/env python

# Copyright (C) 2009 by Akkana Peck.
# You are free to use, share or modify this program under
# the terms of the GPLv2 or, at your option, any later GPL.

VERSION = 0.3

import sys, os, math, datetime, xml.dom.minidom

try:
    import pylab
    have_pylab = True
except ImportError:
    have_pylab = False
    print "pylab isn't installed; will print stats only, no plotting"

#import matplotlib.pyplot as plt
#from matplotlib.dates import YearLocator, MonthLocator, DateFormatter

# Climb threshold: any short climb that doesn't gain more than
# this number of feet won't be counted toward total climb.
# GPS units (at least my Vista CX) have a lot of 1-2'
# fluctuations that vastly overestimate the climb total;
# just swinging the bag around, or unhooking it from your belt
# to check your current position, can do that.
CLIMB_THRESHOLD = 8

# How fast do we have to be moving to count toward the moving average speed?
# This is in miles/hour.
SPEED_THRESHOLD = .5

# The variables we're going to plot:
times = [ ]
eles = [ ]
distances = [ ]

# Some evil globals because python doesn't have static variables:
lastlat = 0
lastlon = 0
lastele = -1
total_dist = 0
total_climb = 0
this_climb = 0
this_climb_start = 0

lasttime = None
moving_time = datetime.timedelta(0)
stopped_time = datetime.timedelta(0)

def getVal(parent, name) :
    nodeList = parent.getElementsByTagName(name)
    if len(nodeList) < 1 :
        return "No " + name + " element"
    children = nodeList[0].childNodes
    if len(children) < 1 :
        return "No " + name + " children"
    node = children[0]
    if node.nodeType != node.TEXT_NODE:
        return name + " isn't a text node"
    return node.data

def accumulate_climb(ele) :
    global lastele, total_climb, this_climb, this_climb_start
    if lastele >= 0 :             # Not the first call
        if ele > lastele :        # Climbed since last step
            if this_climb == 0 :
                this_climb_start = lastele
            this_climb = this_climb + ele - lastele
        else :
            if this_climb > CLIMB_THRESHOLD :
                total_climb = total_climb + this_climb
                this_climb = 0
            elif ele <= this_climb_start :
                # We got a little hump but it isn't big enough to count;
                # probably an artifact like taking the GPS out of its case
                # or getting off the bike or something. So reset.
                this_climb = 0
    lastele = ele

def handleTrackPoint(point) :
    global lastlat, lastlon, lasttime, total_dist, moving_time, stopped_time
    time = datetime.datetime.strptime(getVal(point, "time"),
                                      '%Y-%m-%dT%H:%M:%SZ')
    lat =  float(point.getAttribute("lat"))
    lon = float(point.getAttribute("lon"))
    ele = float(getVal(point, "ele"))
    ele = round(ele * 3.2808399, 2)      # convert from meters to feet

    if lastlat != 0 and lastlon != 0 :
        dist = math.sqrt((lat - lastlat)**2 + (lon - lastlon)**2) * 69.046767
            # 69.046767 converts nautical miles (arcminutes) to miles
        total_dist += dist

        delta_t = time - lasttime   # This is a datetime.timedelta object
        speed = dist / delta_t.seconds * 60 * 60    # miles/hour
        if speed > SPEED_THRESHOLD :
            moving_time += delta_t
            #print "moving\t",
        else :
            stopped_time += delta_t
            #print "stopped\t",

    lasttime = time

    accumulate_climb(ele)

    lastlat = lat
    lastlon = lon

    #print total_dist, ele, "\t", time, lat, lon, "\t", total_climb
    #print total_dist, ele, "\t", time, total_climb

    distances.append(total_dist)
    eles.append(ele)

def handleTrackFile(dom) :
    points = dom.getElementsByTagName("trkpt")
    for i in range (0, len(points), 1) :
        handleTrackPoint(points[i])
    accumulate_climb(0)

def main(args) :
    if len(args) < 2 :
        print "Ellie version", VERSION
        print "Usage:", args[0], file.gpx
        return 1

    #
    # Parse the file:
    #
    try :
        dom = xml.dom.minidom.parse(args[1])
    except IOError, e:
        print e
        #print dir(e)
        return e.errno

    handleTrackFile(dom)

    #
    # Plot the results:
    #
    if not have_pylab :
        print "%.1f miles. Total climb: %d'" % (total_dist, int(total_climb))
        print "%d minutes moving, %d stopped" % (int(moving_time.seconds / 60),
                                                 int(stopped_time.seconds / 60))
        print "Average speed moving: %.1f mph" % (total_dist *60*60
                                                  / moving_time.seconds)
        return 0

    pylab.plot(distances, eles)

    title_string = "Elevation profile (" + str(round(distances[-1], 1)) \
                  + " miles, " + str(int(total_climb)) + "' total climb)"

    pylab.title(title_string)
    pylab.xlabel("miles")
#    pylab.get_current_fig_manager().window.set_title(os.path.basename(args[0] + ": " + title_string))
    pylab.ylabel("feet")
    pylab.grid(True)
    pylab.show()

    pass
    return 0

if __name__ == "__main__" :
    try:
        sys.exit(main(sys.argv))
    except KeyboardInterrupt:
        print " Bye!"
        sys.exit(1)

